import { Service } from "egg";
import { Server as SocketIOServer } from "socket.io";
import { createServer } from "http";
import * as Y from "yjs";
import { Awareness } from "y-protocols/awareness";

// 房间上下文：管理每个房间的 Y.Doc 与 Awareness 状态
type RoomContext = {
  doc: Y.Doc;
  awareness: Awareness;
};

// 1. 基础类型概念
// ArrayBuffer
// 是什么: JavaScript 的原始二进制数据容器
// 特点: 固定长度的字节序列，不能直接操作
// 用途: 网络传输、文件操作的底层数据格式

// Uint8Array
// 是什么: ArrayBuffer 的"视图"，每个元素是 0-255 的无符号整数
// 特点: 可以直接读写字节数据
// 用途: 操作二进制数据的便捷方式

// Buffer (Node.js)
// 是什么: Node.js 的二进制数据处理类，类似 Uint8Array
// 特点: 可以与数据库的 BLOB 字段直接交互
// 用途: 服务器端二进制数据存储和传输
// 1. Buffer 是什么？
// Buffer 是 Node.js 提供的一个全局类，用于处理二进制数据。它在 Node.js 诞生时就存在，比 JavaScript 原生的 ArrayBuffer 和 Uint8Array 更早。
// Buffer 是全局可用的，无需导入
// const buf = Buffer.from('hello');
// console.log(buf); // <Buffer 68 65 6c 6c 6f>
// 2. 为什么需要 Buffer？
// JavaScript 最初是为浏览器设计的，只能处理字符串，无法直接操作二进制数据。Node.js 作为服务器环境，需要处理：
// 文件读写
// 网络传输
// 数据库 BLOB 字段
// 图片、视频等二进制文件
// 3. Buffer 的特点
// 1. 固定大小（创建后不能改变）
// const buf = Buffer.alloc(10);  // 分配 10 字节
// console.log(buf.length);       // 10

// // 2. 直接操作内存
// buf[0] = 0x48;  // 直接设置字节值
// buf[1] = 0x65;  // 'H' = 72, 'e' = 101
// console.log(buf.toString()); // "He"

// // 3. 继承自 Uint8Array
// console.log(buf instanceof Uint8Array); // true
// console.log(buf instanceof Buffer);

// === Buffer ↔ 字符串 ===
// const str = 'Hello';
// const buf = Buffer.from(str, 'utf8');
// const back = buf.toString('utf8');

// // === Buffer ↔ Uint8Array ===
// const uint8 = new Uint8Array([1, 2, 3]);
// const buf = Buffer.from(uint8);        // Uint8Array → Buffer
// const uint8Back = new Uint8Array(buf); // Buffer → Uint8Array

// // === Buffer ↔ ArrayBuffer ===
// const arrayBuf = new ArrayBuffer(10);
// const buf = Buffer.from(arrayBuf);     // ArrayBuffer → Buffer
// const arrayBufBack = buf.buffer.slice(); // Buffer → ArrayBuffer

// // === Buffer ↔ Base64 ===
// const buf = Buffer.from('hello');
// const base64 = buf.toString('base64');  // "aGVsbG8="
// const back = Buffer.from(base64, 'base64');

// Uint8Array → ArrayBuffer
// const uint8Array = new Uint8Array([1, 2, 3, 4]);
// const arrayBuffer: ArrayBuffer = uint8Array.buffer;
// ArrayBuffer → Uint8Array
// const restored = new Uint8Array(arrayBuffer);

// // 1. Yjs 编码：将文档状态编码为二进制
// const state = Y.encodeStateAsUpdate(doc);  // 返回: Uint8Array

// // 2. 数据库存储：转换为 Buffer (Node.js 格式)
// const stateBuffer = Buffer.from(state);    // 返回: Buffer
// // 存储到数据库 LONGBLOB 字段

// // 3. Socket 传输：转换为 ArrayBuffer (Web 标准)
// socket.emit("sync-doc", state.buffer);     // 发送: ArrayBuffer

// // 4. 接收处理：ArrayBuffer → Uint8Array → 应用到 Yjs
// const binary = new Uint8Array(update);     // ArrayBuffer → Uint8Array
// Y.applyUpdate(doc, binary);

// 6. 为什么需要这些转换？
// Yjs: 只认识 Uint8Array 可以用来操作 二进制数据
// 数据库: 只能存储 Buffer/BLOB Buffer:node 二进制处理类
// Socket.io: 传输 ArrayBuffer  存储二进制的容器
// 转换: 确保不同环境间的兼容性

// 5. 完整的数据流
// === 保存文档状态 ===
// const doc = new Y.Doc();                    // Yjs 文档
// const state:Uint8Array = Y.encodeStateAsUpdate(doc);   // → Uint8Array
// const buffer:Buffer = Buffer.from(state);          // 输出: Buffer (数据库格式)
// 然后存到数据库

// // === 恢复文档状态 ===
// const dbBuffer：Buffer = yjsDoc.yjs_state;          // Buffer (从数据库)
// const state = new Uint8Array(dbBuffer);     // → Uint8Array
// Y.applyUpdate(doc, state);                  // → 恢复到 Y.Doc

// // === Socket 传输 ===
// state：Uint8Array state.buffer：ArrayBuffer
// socket.emit("sync-doc", state.buffer);      // → ArrayBuffer (网络)
// socket.on("update", (update: ArrayBuffer) => {
//   const binary：Uint8Array = new Uint8Array(update);    // → Uint8Array
//   Y.applyUpdate(doc, binary);               // → 应用更新
// });

export default class SocketService extends Service {
  private io: SocketIOServer | null = null;

  /**
   * 从 Cookie 请求头解析指定名称的 Cookie 值（无依赖、兜底）
   */
  private getCookieValueFromHeader(cookieHeader: string, name: string): string | null {
    if (!cookieHeader) return null;
    const pairs = cookieHeader
      .split(";")
      .map((c: string) => c.trim())
      .filter(Boolean);
    for (const p of pairs) {
      const idx = p.indexOf("=");
      const k = idx > -1 ? p.slice(0, idx) : p;
      if (k === name) {
        const v = idx > -1 ? decodeURIComponent(p.slice(idx + 1)) : "";
        return v || null;
      }
    }
    return null;
  }

  /**
   * 统一获取明文 SID（优先 Egg cookies，兜底原始 header 手动解析）
   */
  private getSessionIdFromSocket(socket: any): string | null {
    const cookieHeader = (socket.handshake.headers as any)?.cookie || "";
    const anonCtx = this.app.createAnonymousContext({ headers: { cookie: cookieHeader } } as any);
    // 1) 优先使用 Egg 的 cookies（unsigned 明文）
    const fromEgg = anonCtx.cookies.get("SSO_SESSION_ID", { signed: false });
    if (fromEgg) return fromEgg;
    // 2) 兜底手动解析 header
    const fromHeader = this.getCookieValueFromHeader(cookieHeader, "SSO_SESSION_ID");
    return fromHeader;
  }

  /**
   * 通过 SID 校验会话并得到 userId（成功返回字符串，失败返回 null）
   */
  private async getUserIdBySessionId(sessionId: string): Promise<string | null> {
    try {
      const anonCtx = this.app.createAnonymousContext();
      const result = await (anonCtx.service as any).userSequelize.validateSession(sessionId);
      if (result?.success && result?.data?.user?.id) {
        return String(result.data.user.id);
      }
      return null;
    } catch (e) {
      return null;
    }
  }

  /**
   * 从数据库加载文档的 Yjs 状态
   */
  private async loadDocumentYjsState(doc: Y.Doc, documentId: string) {
    try {
      const { models } = this.app as any;

      // 从 yjs_documents 表查找文档状态
      const yjsDoc = await models.YjsDocument.findOne({
        where: { document_id: documentId }
      });

      // 若不存在记录或无状态，保持空文档
      if (!yjsDoc || !yjsDoc.yjs_state) {
        this.app.logger.info("[socket] no persisted state for doc=%s", documentId);
        return;
      }

      // Buffer -> Uint8Array（Sequelize/BLOB 通常返回 Buffer）
      const state = new Uint8Array(yjsDoc.yjs_state as Buffer);
      Y.applyUpdate(doc, state);
      this.app.logger.info("[socket] yjs state restored doc=%s bytes=%d", documentId, state.byteLength);
    } catch (error) {
      console.error(`❌ 加载文档 ${documentId} 的 Yjs 状态失败:`, error);
      // 出错时保持空文档
    }
  }
  /**
   * 保存文档的 Yjs 状态到数据库
   */
  private async saveDocumentYjsState(doc: Y.Doc, documentId: string) {
    try {
      const { models } = this.app as any;

      // 将 Y.Doc 编码为二进制状态
      const state = Y.encodeStateAsUpdate(doc);
      const stateBuffer = Buffer.from(state);

      // 保存或更新 yjs_documents 表
      await models.YjsDocument.upsert({
        document_id: documentId,
        room_name: documentId, // 使用文档ID作为房间名
        yjs_state: stateBuffer,
        last_updated: new Date(),
        version: 1 // 可以考虑实现版本递增
      });

      this.app.logger.info("[socket] yjs state saved doc=%s bytes=%d", documentId, stateBuffer.byteLength);
    } catch (error) {
      console.error(`❌ 保存文档 ${documentId} 的 Yjs 状态失败:`, error);
    }
  }
  /**
   * 统一计算某用户在某文档（房间）下的权限
   * 返回 { effectivePermission: 'read' | 'write' | 'admin', canWrite: boolean, isOwner: boolean }
   */
  private async computeRoomPermission(documentId: string, currentUserId: string | null) {
    const { models } = this.app as any;
    const result = { effectivePermission: "read" as "read" | "write" | "admin", canWrite: false, isOwner: false };
    try {
      const dbDoc = await models.Document.findOne({ where: { id: documentId, status: 1 } });
      const isOwner = Boolean(dbDoc && currentUserId && String(dbDoc.owner_id) === String(currentUserId));
      result.isOwner = isOwner;
      if (isOwner) {
        result.effectivePermission = "admin";
        result.canWrite = true;
        return result;
      }
      if (currentUserId) {
        const collab = await models.DocumentCollaboration.findOne({
          where: { document_id: documentId, user_id: currentUserId }
        });
        const p = (collab?.permission ?? null) as null | "read" | "write" | "admin";
        if (p === "admin") {
          result.effectivePermission = "admin";
          result.canWrite = true;
        } else if (p === "write") {
          result.effectivePermission = "write";
          result.canWrite = true;
        } else if (p === "read") {
          result.effectivePermission = "read";
          result.canWrite = false;
        }
      }
    } catch (err) {
      this.app.logger.warn("[socket] compute permission failed room=%s err=%s", documentId, String(err));
    }
    return result;
  }

  public async init() {
    // 创建独立的 HTTP 服务器用于 Socket.io
    const server = createServer();

    // 存储每个房间对应的文档与光标状态信息：key=roomName -> { doc, awareness }
    const docs: Map<string, RoomContext> = new Map();

    // 获取或初始化房间上下文（仅在首次访问时从数据库恢复一次）
    const getOrCreateRoom = async (roomName: string): Promise<RoomContext> => {
      const existed = docs.get(roomName);
      if (existed) return existed;
      const doc = new Y.Doc();
      const awareness = new Awareness(doc);
      await this.loadDocumentYjsState(doc, roomName);
      const ctx = { doc, awareness } as RoomContext;
      docs.set(roomName, ctx);
      return ctx;
    };

    // 创建 Socket.io 服务器，配置服务器参数
    this.io = new SocketIOServer(server, {
      // CORS 跨域配置：允许前端跨域访问
      cors: {
        origin: ["http://localhost:5173", "http://localhost:5174", "http://127.0.0.1:5173", "http://localhost:3000"], // 明确指定允许的前端域名
        methods: ["GET", "POST"], // 允许的HTTP方法：GET用于握手，POST用于数据传输
        allowedHeaders: ["content-type", "cookie"], // 允许的请求头：包含cookie
        credentials: true // 允许携带凭据：支持cookies、authorization headers等
      },

      // 传输方式：支持WebSocket和HTTP长轮询两种方式
      transports: ["websocket", "polling"],

      // 兼容性设置：允许Engine.IO v3客户端连接到v4服务器（向下兼容）
      allowEIO3: true,

      // 心跳超时时间：60秒内未收到客户端心跳响应则认为连接断开
      pingTimeout: 60000,

      // 心跳间隔时间：每25秒向客户端发送一次心跳包检测连接状态
      pingInterval: 25000
    });

    // 根因是“你在 Cookie 里带的 sid 不是数据库里的明文 sessionId”。所以用它去查 user_sessions.session_id 会查不到用户。
    // === 客户端连接事件监听 ===
    this.io.on("connection", async socket => {
      this.app.logger.info("[socket] connection id=%s", socket.id);

      // 当前连接的房间名（用于断开时退出）
      let currentRoom: string | null = null;
      let joinedProcessed = false;
      // 解析当前连接的用户ID（仅通过 Cookie 明文 SID 校验）
      let currentUserId: string | null = null;
      const sessionId = this.getSessionIdFromSocket(socket);
      this.app.logger.info("[socket] sid=%s", sessionId || "(empty)");
      // 不阻塞事件注册：先并发解析用户ID 不然 会监听不到joinRoom事件
      let resolveUser: Promise<void> | null = null;
      if (sessionId) {
        resolveUser = (async () => {
          currentUserId = await this.getUserIdBySessionId(sessionId);
          this.app.logger.info("[socket] uid=%s", currentUserId || "(null)");
        })();
      } else {
        this.app.logger.info("[socket] uid=%s", "(null)");
      }

      // 处理 joinRoom（确保只处理一次；允许在解析用户期间预先注册）
      const processJoin = async (data: any) => {
        if (joinedProcessed) return;
        joinedProcessed = true;
        // 确保已解析出用户ID（若仍在解析则等待一次）确保先加入房间 后解析
        if (resolveUser) {
          try {
            await resolveUser;
          } catch (err) {
            this.app.logger.warn("[socket] resolve user failed id=%s err=%s", socket.id, String(err));
          }
        }
        this.app.logger.info("[socket] joinRoom payload=%j", data);

        const { roomName } = data || {};
        this.app.logger.info("[socket] joinRoom room=%s uid=%s", roomName, currentUserId || "(null)");
        if (!roomName) {
          socket.emit("error", { message: "房间名称不能为空" });
          return;
        }

        // 保存当前加入的房间名
        currentRoom = roomName;

        // 将 socket 加入到指定的房间，方便后面进行群发
        socket.join(roomName);

        // 获取房间上下文（若首次访问将自动初始化并从数据库恢复）
        const { doc } = await getOrCreateRoom(roomName);
        // 计算并下发权限（默认只读，权限不足也允许同步文档）
        const { effectivePermission, canWrite, isOwner } = await this.computeRoomPermission(roomName, currentUserId);
        this.app.logger.info(
          "[socket] perm room=%s uid=%s perm=%s write=%s owner=%s",
          roomName,
          currentUserId || "(null)",
          effectivePermission,
          String(canWrite),
          String(isOwner)
        );

        // 通知 前端  该用对应改文档的权限
        socket.emit("permission", { permission: effectivePermission, canWrite });

        this.app.logger.info("[socket] perm: %j", {
          roomName,
          uid: currentUserId,
          effectivePermission,
          canWrite,
          isOwner
        });

        // 1. 文档同步 - 服务器需要将文档的状态发送给新加入的客户端
        const state = Y.encodeStateAsUpdate(doc);
        this.app.logger.info("[socket] sync-doc push room=%s bytes=%d", roomName, state.byteLength);
        socket.emit("sync-doc", state.buffer);

        // 2. 监听客户端发送过来的增量更新
        socket.on("update", async (update: ArrayBuffer) => {
          const size = (update as ArrayBuffer)?.byteLength || 0;
          this.app.logger.info(
            "[socket] update recv room=%s uid=%s bytes=%d",
            roomName,
            currentUserId || "(null)",
            size
          );

          // 只读用户禁止写（实时确认权限，避免期间权限变更不生效）
          const { canWrite: allowWriteNow } = await this.computeRoomPermission(roomName, currentUserId);
          this.app.logger.info(
            "[socket] allowWriteNow room=%s uid=%s = %s",
            roomName,
            currentUserId || "(null)",
            String(allowWriteNow)
          );
          if (!allowWriteNow) {
            this.app.logger.warn("[socket] write denied room=%s uid=%s", roomName, currentUserId || "(null)");
            socket.emit("permission-denied", { code: "READ_ONLY", message: "当前为只读权限，禁止修改" });
            return;
          }

          const binary = new Uint8Array(update);
          Y.applyUpdate(doc, binary);
          this.app.logger.info("[socket] update applied room=%s bytes=%d", roomName, binary.byteLength);
          socket.to(roomName).emit("update", binary.buffer);
          await this.saveDocumentYjsState(doc, roomName);
        });

        // 3. 监听客户端发来的 awareness 光标状态
        socket.on("awareness-update", update => {
          // 这边不需要解析，直接广播给其他客户端即可
          socket.to(roomName).emit("awareness-update", update);
        });
      };

      // 统一监听，提前捕获 joinRoom（避免连接早期丢事件）
      socket.onAny((event, ...args) => {
        this.app.logger.info("[socket any] %s %j", event, args);
        if (event === "joinRoom" && !joinedProcessed) {
          void processJoin(args?.[0]);
        }
      });

      // 显式监听（常规路径）
      socket.on("joinRoom", async data => {
        await processJoin(data);
      });

      // 监听离开房间事件
      socket.on("leaveRoom", data => {
        const { roomId } = data;

        if (!roomId) {
          socket.emit("error", { message: "房间ID不能为空" });
          return;
        }

        socket.leave(roomId);
        console.log(`客户端 ${socket.id} 离开房间: ${roomId}`);

        socket.emit("leftRoom", {
          roomId,
          message: `成功离开房间: ${roomId}`,
          timestamp: new Date().toISOString()
        });

        // 通知房间内其他用户
        socket.to(roomId).emit("userLeft", {
          clientId: socket.id,
          roomId,
          message: "用户离开了房间",
          timestamp: new Date().toISOString()
        });
      });

      // === 连接管理事件 ===

      // 监听断开连接事件：客户端主动断开或网络异常时触发
      socket.on("disconnect", reason => {
        this.app.logger.info("[socket] disconnected id=%s reason=%s", socket.id, reason);
        if (currentRoom) {
          socket.leave(currentRoom);
        }
        // 可在此扩展：清理与该 socket 相关的会话/光标状态
      });

      // 监听错误事件：Socket连接或数据传输过程中发生错误时触发
      socket.on("error", error => {
        console.error("Socket运行时错误，Socket ID:", socket.id, "错误信息:", error);

        // TODO: 这里可以添加错误处理逻辑，如：
        // 1. 记录错误日志到文件或数据库
        // 2. 发送错误报告到监控系统
        // 3. 根据错误类型采取相应的恢复措施
      });
    });

    // 启动服务器监听端口 7002（避免与主应用端口冲突）
    const port = 7002;
    server.listen(port, () => {
      console.log(`🚀 Socket.io服务器已启动，端口: ${port}`);
      console.log(`🔗 Socket.io服务: http://localhost:${port}/socket.io/`);
    });

    return this.io;
  }

  public getIO(): SocketIOServer | null {
    return this.io;
  }
}
